Skip to content

feat: PrivateLink support (phase 1)#803

Open
nikagra wants to merge 2 commits intoscylladb:scylla-4.xfrom
nikagra:4.x-privatelink-support-config
Open

feat: PrivateLink support (phase 1)#803
nikagra wants to merge 2 commits intoscylladb:scylla-4.xfrom
nikagra:4.x-privatelink-support-config

Conversation

@nikagra
Copy link

@nikagra nikagra commented Feb 17, 2026

Client Routes Configuration API (Phase 1)

Note: Depends on #java-native-protocol/45

Overview

Implements the configuration API for Client Routes feature to support PrivateLink-style cloud deployments (ScyllaDB Cloud). This is Phase 1 of the implementation - provides programmatic configuration without the full handler implementation.

Jira: DRIVER-88

Changes

New API Classes

  • ClientRoutesEndpoint - Represents a single endpoint with connection ID and optional DNS address
  • ClientRoutesConfig - Immutable configuration container with builder pattern

SessionBuilder Integration

  • Added withClientRoutesConfig(ClientRoutesConfig) method
  • Validates mutual exclusivity with custom AddressTranslator
  • Automatically derives seed hosts from endpoint addresses when no contact points provided
  • Supports IPv4, IPv6, and hostname formats with URI-based parsing

Configuration

  • Added advanced.client-routes.table-name option (default: system.client_routes)
  • Extended ProgrammaticArguments to pass configuration through session initialization
  • Updated reference.conf with inline documentation

Address Parsing

  • URI-based implementation (RFC 3986 compliant)
  • Handles IPv4, IPv6 (bracketed), and hostnames
  • Port validation (1-65535) with helpful error messages
  • Default port: 9042

Supported Address Formats

// IPv4
"192.168.1.1:9042"
"192.168.1.1"         // defaults to 9042

// IPv6 (must use brackets)
"[::1]:9042"
"[2001:db8::1]:9042"
"[::1]"               // defaults to 9042

// Hostname
"my-cluster.scylladb.com:9042"
"my-cluster.scylladb.com"

Usage Example

ClientRoutesConfig config = ClientRoutesConfig.builder()
    .addEndpoint(new ClientRoutesEndpoint(
        UUID.fromString("12345678-1234-1234-1234-123456789012"),
        "my-cluster.us-east-1.aws.scylladb.com:9042"))
    .build();

CqlSession session = CqlSession.builder()
    .withClientRoutesConfig(config)
    .withLocalDatacenter("datacenter1")
    .build();

Testing

  • 10 unit tests - All passing
    • ClientRoutesConfigTest (8 tests)
    • ClientRoutesSessionBuilderTest (2 tests, refactored to use Mockito)
  • 8 validation tests - Address parsing edge cases
    • ClientRoutesValidationTest
  • Test fixes for TypedDriverOptionTest and MapBasedDriverConfigLoaderTest

Documentation

  • Updated manual/core/address_resolution/README.md with Client Routes section
  • Updated reference.conf with configuration options
  • Updated changelog/README.md for version 4.19.0

Validation

✅ All unit tests passing (18 tests total)
✅ No compilation errors
✅ Mutual exclusivity validation working
✅ Seed host derivation working
✅ IPv6 support working
✅ Error messages clear and helpful

What's NOT Included (Future Work)

This PR provides the configuration API only. The following are intentionally deferred to future PRs:

  • ❌ Client Routes Handler implementation (DNS resolution, caching, address translation)
  • ❌ Address Translator V2 API (Host ID-based translation)
  • CLIENT_ROUTES_CHANGE event handling (stub exists in ControlConnection)
  • ❌ Control connection event registration
  • ❌ Integration tests with mock cluster

Design Decisions

  1. Programmatic-only configuration - Connection IDs are sensitive; no file-based config
  2. Mutual exclusivity with AddressTranslator - Prevents conflicts
  3. URI-based parsing - Standards-compliant, handles IPv6 correctly, simple implementation
  4. Immutable configuration - Thread-safe, prevents accidental modifications
  5. Mockito for tests - Eliminated 92 lines of boilerplate code

Breaking Changes

None - this is a purely additive change.

Migration Guide

N/A - New feature, no migration needed.

Review Checklist

  • API design review (immutability, builder pattern, validation)
  • SessionBuilder integration review
  • URI-based address parsing correctness
  • Test coverage adequate
  • Documentation clarity
  • Error messages helpful
  • No regressions in existing tests

Related

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements Phase 1 of PrivateLink support for the ScyllaDB Java Driver, introducing a configuration API for Client Routes to support AWS PrivateLink-style deployments. The implementation provides programmatic configuration for connection endpoints without the full handler implementation (deferred to future phases).

Changes:

  • Introduced new API classes ClientRoutesConfig and ClientRoutesEndpoint for programmatic endpoint configuration
  • Extended SessionBuilder with withClientRoutesConfig() method, including validation for mutual exclusivity with custom AddressTranslator and automatic seed host derivation
  • Added configuration option advanced.client-routes.table-name with supporting infrastructure in DefaultDriverOption, TypedDriverOption, and OptionsMap
  • Implemented URI-based address parsing supporting IPv4, IPv6 (bracketed), and hostname formats with port validation
  • Extended ProgrammaticArguments to pass client routes configuration through session initialization
  • Added comprehensive unit tests covering configuration builder, validation, and address parsing scenarios
  • Updated documentation in manual/core/address_resolution/README.md explaining client routes usage and constraints

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
pom.xml Updates native-protocol dependency to SNAPSHOT version for CLIENT_ROUTES support
core/src/main/java/com/datastax/oss/driver/api/core/config/ClientRoutesEndpoint.java New immutable API class representing a single endpoint with connection ID and optional DNS address
core/src/main/java/com/datastax/oss/driver/api/core/config/ClientRoutesConfig.java New immutable configuration container with builder pattern for managing client routes endpoints
core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java Adds CLIENT_ROUTES_TABLE_NAME option for system table configuration
core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java Adds typed wrapper for CLIENT_ROUTES_TABLE_NAME option
core/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.java Sets default value "system.client_routes" for client routes table name
core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java Integrates client routes configuration with validation, mutual exclusivity checks, and automatic seed host derivation using URI-based address parsing
core/src/main/java/com/datastax/oss/driver/api/core/session/ProgrammaticArguments.java Extends to pass ClientRoutesConfig through session initialization
core/src/main/resources/reference.conf Documents client-routes configuration section with table-name option
core/src/test/java/com/datastax/oss/driver/api/core/config/ClientRoutesConfigTest.java Unit tests for ClientRoutesConfig builder functionality
core/src/test/java/com/datastax/oss/driver/api/core/session/ClientRoutesSessionBuilderTest.java Tests for SessionBuilder integration with client routes
core/src/test/java/com/datastax/oss/driver/internal/core/session/ClientRoutesValidationTest.java Comprehensive validation tests for address parsing edge cases
manual/core/address_resolution/README.md Documentation for client routes feature with usage examples and constraints

@nikagra nikagra force-pushed the 4.x-privatelink-support-config branch 4 times, most recently from d61145d to 17991f3 Compare February 18, 2026 14:38
… deployments

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.


import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator;
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddressTranslator is imported but only referenced in Javadoc; Java treats that as unused, which can trigger unused-import checks and adds noise. Remove the import or reference the type in code (e.g., via a signature/field) if it’s intentionally needed.

Suggested change
import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator;

Copilot uses AI. Check for mistakes.
Comment on lines +970 to +975
} catch (Exception e) {
throw new IllegalArgumentException(
String.format(
"Failed to parse client routes endpoint address '%s' (connection ID: %s): %s",
addr, endpoint.getConnectionId(), e.getMessage()),
e);
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The catch block wraps any exception from AddressParser and embeds e.getMessage() into a new message; since AddressParser already formats a full contextual message (including connectionId), this produces duplicated/verbose errors. Prefer letting IllegalArgumentException propagate as-is, or wrap without repeating the nested message; also narrow the catch to the expected exception type instead of Exception.

Suggested change
} catch (Exception e) {
throw new IllegalArgumentException(
String.format(
"Failed to parse client routes endpoint address '%s' (connection ID: %s): %s",
addr, endpoint.getConnectionId(), e.getMessage()),
e);
} catch (IllegalArgumentException e) {
throw e;

Copilot uses AI. Check for mistakes.
String.format("Invalid port %d. Port must be between 1 and 65535.", port)));
}

return new InetSocketAddress(host, port);
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseContactPoint returns new InetSocketAddress(host, port), which may trigger DNS resolution during parsing and only captures a single resolved address. To avoid blocking I/O during builder/config parsing and to let callers control resolution (and potentially expand multiple A-records), consider returning an unresolved address (InetSocketAddress.createUnresolved) or exposing host/port separately.

Suggested change
return new InetSocketAddress(host, port);
return InetSocketAddress.createUnresolved(host, port);

Copilot uses AI. Check for mistakes.
Comment on lines +155 to +156
this.endpoints.clear();
this.endpoints.addAll(endpoints);
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withEndpoints(List<...>) clears and addAlls without validating that the list is non-empty and contains no null elements, even though the Javadoc states “must not be null or empty”. A null element can later cause NPEs (e.g., when iterating endpoints in SessionBuilder). Validate emptiness and reject null elements (or defensively copy with per-element null checks).

Suggested change
this.endpoints.clear();
this.endpoints.addAll(endpoints);
if (endpoints.isEmpty()) {
throw new IllegalArgumentException("endpoints must not be empty");
}
this.endpoints.clear();
for (ClientRoutesEndpoint endpoint : endpoints) {
addEndpoint(endpoint);
}

Copilot uses AI. Check for mistakes.
@nikagra nikagra marked this pull request as ready for review February 18, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments